using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace HandyIRCCore
{
    public class Core
    {
        #region Declarations
        
        private string serverAddress = "localhost";
        
        private int serverPort = 6667;
        
        private bool isConnected = false;
        
        private StreamReader readerStream;
        
        private StreamWriter writerStream;

        private TcpClient tcpClient;

        private Encoding encoding = Encoding.GetEncoding("ISO-8859-1");

        private int socketReceiveTimeout = 600;

        private int socketSendTimeout = 600;

        private Thread workingThread;

        private string userNick = "HandyUser";

        private string userName = "Unknown";

        private string userRealName = "Unknown";

        private string userPass = "";

        private int userMode = 0;

        #region Delegates and Events

        public delegate void ReceiveMessageEventHandler(object sender, HandyIRCMessage msg);

        public event ReceiveMessageEventHandler ReceiveMessageEvent;

        public delegate void JoinEventHandler(object sender, HandyIRCMessage msg);

        public event JoinEventHandler JoinEvent;

        public delegate void PartEventHandler(object sender, HandyIRCMessage msg);

        public event PartEventHandler PartEvent;

        public delegate void KickEventHandler(object sender, HandyIRCMessage msg);

        public event KickEventHandler KickEvent;

        public delegate void QuitEventHandler(object sender, HandyIRCMessage msg);

        public event QuitEventHandler QuitEvent;

        public delegate void NickEventHandler(object sender, HandyIRCMessage msg);

        public event NickEventHandler NickEvent;

        public delegate void PrivmsgEventHandler(object sender, HandyIRCMessage msg);

        public event PrivmsgEventHandler PrivmsgEvent;

        public delegate void RPL_NAMREPLYEventHandler(object sender, HandyIRCMessage msg);

        public event RPL_NAMREPLYEventHandler RPL_NAMREPLYEvent;

        #endregion

        #endregion

        #region Methods

        public Core()
        {
            workingThread = new Thread(new ThreadStart(Listen));
        }

        public void Connect()
        {
            Connect(serverAddress, serverPort);
        }

        public void Connect(string address, int port)
        {
            Connect(address, port, userNick, userName, userRealName, userPass, userMode);
        }

        public void Connect(string address, int port, 
                            string nick, string name, string realName, string pass, int mode)
        {
            if (isConnected)
                throw new AlreadyConnectedException();

            serverAddress = address;
            serverPort = port;

            userNick = nick;
            userName = name;
            userRealName = realName;
            userPass = pass;
            userMode = mode;

            try
            {
                System.Net.IPAddress ip = System.Net.Dns.GetHostEntry(serverAddress).AddressList[0];
                tcpClient = new TcpClient();
                tcpClient.NoDelay = true;
                tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
                tcpClient.ReceiveTimeout = socketReceiveTimeout * 1000;
                tcpClient.SendTimeout = socketSendTimeout * 1000;
                tcpClient.Connect(ip, serverPort);
            }
            catch (SocketException exc)
            {
                throw new CannotConnectException(exc.Message);
            }

            readerStream = new StreamReader(tcpClient.GetStream(), encoding);
            writerStream = new StreamWriter(tcpClient.GetStream(), encoding);

            isConnected = true;

            workingThread.Start();

            Register();
        }

        public void Disconnect()
        {
            if (!isConnected)
                throw new NotConnectedException();

            if (workingThread.IsAlive)
                workingThread.Abort();

            tcpClient.Close();

            isConnected = false;
        }

        public void SendMessage(string message)
        {
            try
            {
                if (isConnected)
                {
                    writerStream.Write(message + Environment.NewLine);
                    writerStream.Flush();
                }
            }
            catch (IOException exc)
            {
                throw new CannotSendMessageException(exc.Message);
            }
        }

        private void Listen()
        {
            try
            {
                while (isConnected)
                {
                    string rawMessage = readerStream.ReadLine();

                    if (rawMessage != null)
                    {
                        HandyIRCMessage message = new HandyIRCMessage(rawMessage);

                        ProcessMessage(message);

                        if (ReceiveMessageEvent != null)
                            ReceiveMessageEvent(this, message);
                    }
                }
            }
            catch (IOException exc)
            {
                throw new CannotReadMessageException(exc.Message);
            }
        }

        private void Register()
        {
            if (isConnected)
            {
                if (userPass.Length != 0)
                    SendMessage("PASS " + userPass);
                SendMessage("NICK " + userNick);
                SendMessage("USER " + userName + " " + userMode.ToString() + " * :" + userRealName);
            }
        }

        private void ProcessMessage(HandyIRCMessage message)
        {
            switch (message.Command)
            {
                // messages:
                
                case "PING":
                    // ping? pong!
                    SendMessage("PONG " + message.Parameters[0]);
                    break;
                case "PRIVMSG":
                    if (PrivmsgEvent != null)
                        PrivmsgEvent(this, message);
                    break;
                case "JOIN":
                    if (JoinEvent != null)
                        JoinEvent(this, message);
                    break;
                case "PART":
                    if (PartEvent != null)
                        PartEvent(this, message);
                    break;
                case "KICK":
                    if (KickEvent != null)
                        KickEvent(this, message);
                    break;
                case "QUIT":
                    if (QuitEvent != null)
                        QuitEvent(this, message);
                    break;
                case "NICK":
                    userNick = message.Parameters[0];
                    if (NickEvent != null)
                        NickEvent(this, message);
                    break;

                // todo: implement other cases

                // numeric replies:

                case "353":
                    if (RPL_NAMREPLYEvent != null)
                        RPL_NAMREPLYEvent(this, message);
                    break;

                // todo: implement other cases
            }
        }

        #endregion

        #region Properties

        public string ServerAddress
        {
            get
            {
                return serverAddress;
            }
            
            set
            {
                serverAddress = value;
            }
        }
        
        public int ServerPort
        {
            get
            {
                return serverPort;
            }
            
            set
            {
                serverPort = value;
            }
        }
        
        public bool IsConnected
        {
            get
            {
                return isConnected;
            }
        }
        
        public Encoding Encoding
        {
            get
            {
                return encoding;
            }
            
            set
            {
                encoding = value;
            }
        }

        public int SocketReceiveTimeout
        {
            get
            {
                return socketReceiveTimeout;
            }

            set
            {
                socketReceiveTimeout = value;
            }
        }

        public int SocketSendTimeout
        {
            get
            {
                return socketSendTimeout;
            }

            set
            {
                socketSendTimeout = value;
            }
        }

        public string UserNick
        {
            get
            {
                return userNick;
            }

            set
            {
                userNick = value;
            }
        }

        public string UserName
        {
            get
            {
                return userName;
            }

            set
            {
                userName = value;
            }
        }

        public string UserRealName
        {
            get
            {
                return userRealName;
            }

            set
            {
                userRealName = value;
            }
        }

        public string UserPassword
        {
            get
            {
                return userPass;
            }

            set
            {
                userPass = value;
            }
        }

        public int UserMode
        {
            get
            {
                return userMode;
            }

            set
            {
                userMode = value;
            }
        }
        
        #endregion
    }
}
